 /*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Merlion.MRP.bean;

import Merlion.CRM.entity.SalesDocumentEntity;
import Merlion.MRP.entity.BackOrderEntity;
import Merlion.MRP.entity.ConsolidatedMonthlySalesRecordEntity;
import Merlion.MRP.entity.MonthlySalesRecordEntity;
import Merlion.MRP.entity.PlannedDailyDemandEntity;
import Merlion.MRP.entity.PlannedMonthlyDemandEntity;
import Merlion.MRP.entity.PlannedWeeklyDemandEntity;
import Merlion.MRP.entity.ReservationEntity;
import Merlion.MRP.entity.UnplannedDailyDemandEntity;
import Merlion.SCM.bean.InventoryItemsBeanRemote;
import Merlion.SCM.entity.InventoryBatchEntity;
import Merlion.SCM.entity.MaterialEntity;
import Merlion.SCM.entity.ProductEntity;
import Merlion.util.state.ATPCheckState;
import Merlion.util.state.InventoryState;
import Merlion.util.state.PlannedDemandState;
import Merlion.util.state.SOPBreakdownState;
import Merlion.util.state.SalesOperationPlanState;
import Merlion.util.state.SalesRecordState;
import java.lang.Object;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Calendar;
import java.util.Date;
import javax.ejb.EJB;
import javax.persistence.Query;

@Stateless
public class DemandManagementBean implements DemandManagementBeanRemote {

    @PersistenceContext
    EntityManager em;
    @EJB
    ProductionManagementBeanRemote pmb;
    @EJB
    InventoryItemsBeanRemote iib;
    //String //message;
    //Status//message //messages = new Status//message();

    /*
    @Override protected Long getIdFromyearNum(int monthNum, int yearNum) {
    Long id;
    Query q = em.createQuery("Select s.id from SalesRecordEntity s where s.monthNum = '" + monthNum + "' and s.yearNum = '" + yearNum + "'");
    try {
    id = (Long) q.getSingleResult();
    //message = "ID successfully retrieved";
    //messages.add//message(//message);
    return id;
    } catch (NoResultException e) {
    //message = "Failed to retrieve ID";
    //messages.add//message(//message);
    return id = null;
    }
    } */
    protected int[] getWorkingDaysInWeeksInMonth(Integer yearInt, Integer monthInt) {

        if (monthInt > 12 || monthInt < 1) {
            throw new IllegalArgumentException("Please insert value from 1-12 for month");
        } else {
            monthInt--;
        }

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, yearInt);
        cal.set(Calendar.MONTH, monthInt);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        cal.setFirstDayOfWeek(Calendar.MONDAY);
        cal.setMinimalDaysInFirstWeek(1);

        //System.out.println("Date set to " + cal.getTime().toString());
        int last = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        int nweek = 1;

        int count[] = {0, 0, 0, 0, 0, 0};

        for (int i = 0; i < last; i++) {
            if (!(cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)) {
                count[nweek - 1]++;
            }
            cal.add(Calendar.DAY_OF_MONTH, 1);
            if (cal.get(Calendar.WEEK_OF_MONTH) > nweek) {
                nweek = cal.get(Calendar.WEEK_OF_MONTH);
            }
        }

        return count;
    }

    protected int getWorkingDaysNumInMonth(Integer yearInt, Integer monthInt) {

        if (monthInt > 12 || monthInt < 1) {
            throw new IllegalArgumentException("Please insert value from 1-12 for month");
        } else {
            monthInt--;
        }

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, yearInt);
        cal.set(Calendar.MONTH, monthInt);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        //System.out.println("Date set to " + cal.getTime().toString());
        int last = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        int count = 0;

        for (int i = 0; i < last; i++) {
            if (!(cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)) {
                count++;
            }
            cal.add(Calendar.DAY_OF_MONTH, 1);
            //System.out.println("Date set to " + cal.getTime().toString());
        }

        return count;
    }

    protected Date calculateEndDate(Date startDate, int noOfWorkingDays) {
        Calendar cal = Calendar.getInstance();

        cal.setTime(startDate);

        for (int i = 0; i < noOfWorkingDays; i++) {
            while (cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
                cal.add(Calendar.DAY_OF_MONTH, 1);
            }
            cal.add(Calendar.DAY_OF_MONTH, 1);
        }

        return cal.getTime();
    }


    /*  
     *   @Override protected Method that return Monthly Sales Record for a particular Year and Month
     *  @param yearInt - the year of the sales record
     *  @param monthInt - the month of the sales record
     * 
     *  return MonthlySalesRecordEntity
     */
    protected MonthlySalesRecordEntity getMonthlySalesRecord(Long productId, Integer yearInt, Integer monthInt) {
        String id = StringUtils.leftPad(productId.toString(), 4, "0") + "/" + yearInt.toString() + StringUtils.leftPad(monthInt.toString(), 2, "0");
        //message = "MonthlySalesRecord fetched, ID: " + id;
        //messages.add//message(//message);
        MonthlySalesRecordEntity msr = em.find(MonthlySalesRecordEntity.class, id);

        if (msr == null) {
            return null;
            //throw new NullPointerException("Monthly Sales Record does not exist");
        }
        return msr;
    }

    /*
     *  @Override protected Method that return the Planned Monthly Demand Entity for a specified product and period
     * @param productId - the id of the product
     * @param yearInt - the year of the planned demand
     * @param monthInt - the month of the planned demand
     * 
     * return PlannedMonthlyDemandEntity
     */
    //@Override
    protected PlannedMonthlyDemandEntity getPlannedMonthlyDemand(Long productId, Integer yearInt, Integer monthInt) {
        System.out.println("Getting Planned Monthly Demand for product id: " + productId + ", for " + yearInt + "-" + monthInt);

        ProductEntity product = em.find(ProductEntity.class, productId);
        System.out.println("REACHED");
        if (product == null) {
            System.out.println("Failed to fetch planned monthly demand");
            return null;
        }

        String id = "PMD-FIN" + product.getId().toString() + "Y" + yearInt.toString() + "M" + monthInt.toString();

        PlannedMonthlyDemandEntity pmd = em.find(PlannedMonthlyDemandEntity.class, id);
        /*
        PlannedMonthlyDemandEntity pmd = null;
        for (PlannedMonthlyDemandEntity pmdTemp : msr.getPlannedMonthlyDemands()) {
        if (product.equals(pmdTemp.getProduct())) {
        pmd = pmdTemp;
        break;
        }
        }
         */

        return pmd;
    }

    /*
     *  @Override protected Method that return the Planned Weekly Demand Entity for a specified product and period
     * @param productId - the id of the product
     * @param yearInt - the year of the planned demand
     * @param monthInt - the month of the planned demand
     * @param weekOfMonth - indicate which week of the month is the planned demand for
     * 
     * return PlannedWeeklyDemandEntity
     */
    //@Override
    protected PlannedWeeklyDemandEntity getPlannedWeeklyDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth) {
        System.out.println("Getting corresponding planned monthly demand.. ");
        PlannedMonthlyDemandEntity pmd = this.getPlannedMonthlyDemand(productId, yearInt, monthInt);
        if (pmd == null) {
            throw new NullPointerException("Cannot find the corresponding Planned Monthly Demand");
        }
        System.out.println(pmd.toString() + " successfully fetched!");
        String id = "PWD-FIN" + productId.toString() + "Y" + yearInt.toString() + "M" + monthInt.toString() + "W" + weekOfMonth.toString();

        PlannedWeeklyDemandEntity pwd = em.find(PlannedWeeklyDemandEntity.class, id);

        List<PlannedWeeklyDemandEntity> pwds = new ArrayList<PlannedWeeklyDemandEntity>();
        pwds = (List<PlannedWeeklyDemandEntity>) pmd.getPlannedWeeklyDemandEntities();

        if (!pwds.contains(pwd) && pwd != null) {
            throw new IllegalStateException("The Planned Weekly Demand is not contained within the supposed Planned Monthly Demand");
        }

        return pwd;
    }

    protected ConsolidatedMonthlySalesRecordEntity getConsolidatedMonthlySalesRecord(Integer yearInt, Integer monthInt) {
        String stringId = "CMSR" + yearInt.toString() + "-" + monthInt;

        ConsolidatedMonthlySalesRecordEntity cmsr = em.find(ConsolidatedMonthlySalesRecordEntity.class, stringId);

        return cmsr;
    }
    /*
     *  @Override protected Method that return the Planned Daily Demand Entity for a specified product and period
     * @param productId - the id of the product
     * @param yearInt - the year of the planned demand
     * @param monthInt - the month of the planned demand
     * @param weekOfMonth - indicate which week of the month is the planned demand for
     * @param dayOfMonth -indicate which day of the month is the planned demand for
     * 
     * return PlannedDailyDemandEntity
     */

    //@Override
    protected PlannedDailyDemandEntity getPlannedDailyDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth, Integer dayOfMonth) {
        System.out.println("Getting corresponding planned weekly demand... ");
        PlannedWeeklyDemandEntity pwd = this.getPlannedWeeklyDemand(productId, yearInt, monthInt, weekOfMonth);

        if (pwd == null) {
            throw new NullPointerException("Cannot find the corresponding Planned Weekly Demand");
        }
        System.out.println(pwd.toString() + " successfully fetched!");
        String id = "PDD-FIN" + productId.toString() + "Y" + yearInt.toString() + "M" + monthInt.toString() + "W" + weekOfMonth.toString() + "D" + dayOfMonth.toString();

        PlannedDailyDemandEntity pdd = em.find(PlannedDailyDemandEntity.class, id);

        List<PlannedDailyDemandEntity> pdds = new ArrayList();
        pdds = (List<PlannedDailyDemandEntity>) pwd.getPlannedDailyDemandEntities();
        if (!pdds.contains(pdd)) {
            throw new IllegalStateException("The Planned Daily Demand is not contained within the supposed Planned WeeklyDemand");
        }

        return pdd;
    }

    /*
     * Public method to create Annual Sales Record (using actual sales figures)
     */
    /*
    @Override
    public boolean createAnnualSalesRecord(Integer yearInt, int actualBase, int actualPromotion, int actualSales) {
    
    AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
    
    if (asr != null) {
    //message = "createAnnualSalesRecord: create failed. Annual Sales Record already exists.";
    //messages.add//message(//message);
    return false;
    }
    
    AnnualSalesRecordEntity newAsr = new AnnualSalesRecordEntity(yearInt);
    em.persist(newAsr);
    newAsr.setActualBase(actualBase);
    newAsr.setActualPromotion(actualPromotion);
    newAsr.setActualSales(actualSales);
    em.flush();
    
    //message = "createAnnualSalesRecord: create successful.";
    //messages.add//message(//message);
    return true;
    }
    
    /*
     * Public method to update Annual Sales Record (actual figures)
     */
    /*
    @Override
    public boolean updateAnnualSalesRecord(Integer yearInt, int actualBase, int actualPromotion, int actualSales) {
    
    AnnualSalesRecordEntity asr = new AnnualSalesRecordEntity(yearInt);
    
    if (asr == null) {
    throw new NullPointerException("updateAnnualSalesRecord: Annual Sales Record not found.");
    }
    
    asr.setActualBase(actualBase);
    asr.setActualPromotion(actualPromotion);
    asr.setActualSales(actualSales);
    em.flush();
    
    //message = "updateAnnualSalesRecord: Update successful.";
    //messages.add//message(//message);
    return true;
    }
    
    /*
     * Public method to delete Annual Sales Record, force deletion possible
     */
    /*
    @Override
    public boolean deleteAnnualSalesRecord(Integer yearInt, boolean force) {
    AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
    
    if (asr == null) {
    throw new NullPointerException("deleteAnnualSalesRecord: Annual Sales Record not found.");
    }
    
    ArrayList<MonthlySalesRecordEntity> msrs = (ArrayList<MonthlySalesRecordEntity>) asr.getMonthlySalesRecords();
    
    if (msrs.isEmpty()) {
    em.detach(asr);
    em.flush();
    //message = "deleteAnnualSalesRecord: Delete successful.";
    //messages.add//message(//message);
    return true;
    }
    
    if (!force) {
    //message = "deleteAnnualSalesRecord: Children Monthly Sales Record Exist.";
    //messages.add//message(//message);
    return false;
    } else {
    for (MonthlySalesRecordEntity msr : msrs) {
    if (msrs.remove(msr)) {
    asr.setMonthlySalesRecords(msrs);
    em.detach(msr);
    } else {
    throw new RuntimeException("deleteAnnualSalesRecord: Unexpected Exception.");
    }
    
    }
    em.flush();
    em.detach(asr);
    em.flush();
    //message = "deleteAnnualSalesRecord: Delete successful.";
    //messages.add//message(//message);
    return true;
    }
    }
    
    /*
     * Public method to create Monthly Sales Record (using actual sales figures)
     */
    @Override
    public boolean createMonthlySalesRecord(Long productId, Integer yearInt, Integer monthInt, int actualBase, int actualPromotion, int actualSales) {

        ProductEntity product = em.find(ProductEntity.class, productId);
        //AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
        MonthlySalesRecordEntity msr = this.getMonthlySalesRecord(productId, yearInt, monthInt);

        if (product == null) {
            throw new NullPointerException("Invalid product specified.");
        }

        /*
        if (msr != null) {
        //message = "createMonthlySalesRecord: create failed. Monthly Sales Record already exists.";
        //messages.add//message(//message);
        throw new IllegalStateException("MonthlySalesRecord already exists.");
        }*/

        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(yearInt, monthInt);
        ConsolidatedMonthlySalesRecordEntity fmsr = this.getConsolidatedMonthlySalesRecord(yearInt + 1, monthInt);
        if (msr == null) {
            msr = new MonthlySalesRecordEntity(product, yearInt, monthInt);
            em.persist(msr);
        }

        if (cmsr == null) {
            System.out.println("creating new consolidated monthly sales record");
            cmsr = new ConsolidatedMonthlySalesRecordEntity(yearInt, monthInt);
            em.persist(cmsr);
        } else {
            System.out.println("updating consolidated monthly sales record");
        }

        if (fmsr == null) {
            System.out.println("creating following year consolidated monthly sales record");
            fmsr = new ConsolidatedMonthlySalesRecordEntity(yearInt + 1, monthInt);
            em.persist(fmsr);
        } else {
            System.out.println("updating following year consolidated monthly sales record");
        }

        msr.setActualBase(actualBase);
        msr.setActualPromotion(actualPromotion);
        msr.setActualSales(actualSales);
        em.flush();
        List<MonthlySalesRecordEntity> msrs = new ArrayList();
        msrs = (List<MonthlySalesRecordEntity>) cmsr.getMonthlySalesRecords();

        if (msrs == null || !msrs.contains(msr)) { //defensive strategy
            msrs.add(msr);
            cmsr.setMonthlySalesRecords(msrs);
            cmsr.setActualBase(cmsr.getActualBase() + actualBase);
            cmsr.setActualPromotion(cmsr.getActualPromotion() + actualPromotion);
            cmsr.setActualSales(cmsr.getActualSales() + actualSales);
            fmsr.setPreviousBase(fmsr.getPreviousBase() + actualBase);
            fmsr.setPreviousPromotion(fmsr.getPreviousPromotion() + actualPromotion);
            fmsr.setPreviousSales(fmsr.getPreviousSales() + actualSales);
        }



        em.flush();

        return true;
        /*
        if (asr == null) { // create both annual and monthly sales record
        AnnualSalesRecordEntity newAsr = new AnnualSalesRecordEntity(yearInt);
        em.persist(newAsr);
        MonthlySalesRecordEntity newMsr = new MonthlySalesRecordEntity(newAsr, monthInt);
        em.persist(newMsr);
        
        newMsr.setActualBase(actualBase);
        newMsr.setActualPromotion(actualPromotion);
        newMsr.setActualSales(actualSales);
        
        Collection<MonthlySalesRecordEntity> msrs = new ArrayList<MonthlySalesRecordEntity>();
        msrs.add(newMsr);
        newAsr.setMonthlySalesRecords(msrs);
        
        em.flush();
        
        //message = "New Annual and Monthly Sales Records created the specified time period.";
        //messages.add//message(//message);
        return true;
        
        } else { //create new monthly sales record and update the annual sales record
        MonthlySalesRecordEntity newMsr = new MonthlySalesRecordEntity(asr, monthInt);
        em.persist(newMsr);
        
        newMsr.setActualBase(actualBase);
        newMsr.setActualPromotion(actualPromotion);
        newMsr.setActualSales(actualSales);
        
        Collection<MonthlySalesRecordEntity> msrs = new ArrayList<MonthlySalesRecordEntity>();
        msrs.add(newMsr);
        asr.setMonthlySalesRecords(msrs);
        
        em.flush();
        
        //message = "New Monthly Sales Records created the specified time period.";
        //messages.add//message(//message);
        return true;
        }*/

    }

    /*
     * Public method to update Monthly Sales Record (actual sales figures)
     */
    @Override
    public boolean updateMonthlySalesRecord(Long productId, Integer yearInt, Integer monthInt, int actualBase, int actualPromotion, int actualSales, boolean status) {
        MonthlySalesRecordEntity msr = this.getMonthlySalesRecord(productId, yearInt, monthInt);

        if (msr == null) {
            throw new NullPointerException("Monthly Sales Record do not exist.");
        }

        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(yearInt, monthInt);
        ConsolidatedMonthlySalesRecordEntity fmsr = this.getConsolidatedMonthlySalesRecord(yearInt + 1, monthInt);

        int diffBase = actualBase - msr.getActualBase();
        int diffPromotion = actualPromotion - msr.getActualPromotion();
        int diffSales = actualSales - msr.getActualSales();

        cmsr.setActualBase(cmsr.getActualBase() + diffBase);
        cmsr.setActualPromotion(cmsr.getActualPromotion() + diffPromotion);
        cmsr.setActualSales(cmsr.getActualSales() + diffSales);
        fmsr.setPreviousBase(fmsr.getPreviousBase() + diffBase);
        fmsr.setPreviousPromotion(fmsr.getPreviousPromotion() + diffPromotion);
        fmsr.setPreviousSales(fmsr.getPreviousSales() + diffSales);
        msr.setActualBase(actualBase);
        msr.setActualPromotion(actualPromotion);
        msr.setActualSales(actualSales);
        msr.setConverted(status);

        em.flush();

        //message = "MonthlySalesRecord updated";
        //messages.add//message(//message);

        return true;

    }

    /*
     * Public method to delete monthly sales record
     */
    @Override
    public boolean deleteMonthlySalesRecord(Long productId, Integer yearInt, Integer monthInt) {

        MonthlySalesRecordEntity msr = this.getMonthlySalesRecord(productId, yearInt, monthInt);

        if (msr == null) {
            throw new NullPointerException("Monthly Sales Record do not exist.");
        }

        /*
        AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
        ArrayList<MonthlySalesRecordEntity> msrs = (ArrayList) asr.getMonthlySalesRecords();
        
        if (!msrs.remove(msr)) {
        throw new NullPointerException("Cannot remove Monthly Sales Record from the Annual Sales Record");
        }
        
        asr.setMonthlySalesRecords(msrs);
         * 
         */
        em.remove(msr);
        em.flush();

        return true;

    }

    @Override
    public void deleteMonthlySalesRecord(String recordId) {

        MonthlySalesRecordEntity msr = em.find(MonthlySalesRecordEntity.class, recordId);

        if (msr == null) {
            throw new NullPointerException("Monthly Sales Record do not exist.");
        } else {

            /*
            AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
            ArrayList<MonthlySalesRecordEntity> msrs = (ArrayList) asr.getMonthlySalesRecords();
            
            if (!msrs.remove(msr)) {
            throw new NullPointerException("Cannot remove Monthly Sales Record from the Annual Sales Record");
            }
            
            asr.setMonthlySalesRecords(msrs);
             * 
             */

            em.remove(msr);
            em.flush();
            System.out.println("Monthly Sales Record is successfully deleted");
        }

    }

    /*
     * Public method that forecast annual sales from year-1 figures
     * 
     *
    @Override
    public boolean forecastAnnualSales(Integer yearInt, double growthRate, int projectedPromotion) {
    int projectedSales;
    AnnualSalesRecordEntity history = em.find(AnnualSalesRecordEntity.class, yearInt - 1);
    
    if (history == null) { //Historical data doesn't exist, quit immediately
    //message = "Unable to find historical sales figure.";
    //messages.add//message(//message);
    return false;
    }
    
    //Previous Annual Sales Record Exist
    int projectedGrowth = (int) (history.getActualBase() * growthRate / 100);
    int projectedBase = history.getActualBase() + projectedGrowth;
    projectedSales = projectedBase + projectedPromotion;
    
    AnnualSalesRecordEntity current = em.find(AnnualSalesRecordEntity.class, yearInt);
    
    if (current == null) { //create Current Annual Sales Record if not exist
    current = new AnnualSalesRecordEntity(yearInt);
    em.persist(current);
    //message = "New Sales Record Created.";
    //messages.add//message(//message);
    }
    
    current.setProjectedBase(projectedBase);
    current.setProjectedGrowth(projectedGrowth);
    current.setProjectedPromotion(projectedPromotion);
    current.setProjectedSales(projectedSales);
    em.flush();
    
    //message = "Sales Projection forecasted.";
    //messages.add//message(//message);
    
    return true;
    }
     * *
     * 
     */
    @Override
    public void updateSalesCompositionRatios() {
        Query q = em.createQuery("SELECT a from ConsolidatedMonthlySalesRecordEntity a");
        for (Object o : q.getResultList()) {
            ConsolidatedMonthlySalesRecordEntity cmsr = (ConsolidatedMonthlySalesRecordEntity) o;
            List<MonthlySalesRecordEntity> msrs = new ArrayList();
            msrs = (List<MonthlySalesRecordEntity>) cmsr.getMonthlySalesRecords();
            List<Object[]> compositions = new ArrayList();
            for (MonthlySalesRecordEntity msr : msrs) {
                int ratio = (int) msr.getActualSales() / cmsr.getActualSales();
                msr.setRatio(ratio);
                Object[] details = new Object[2];
                details[0] = msr.getProduct().getName();
                details[1] = ratio;
                compositions.add(details);
                em.flush();
            }
            cmsr.setCompositions(compositions);
            em.flush();
        }

    }

    /*
     * Forecast Sales
     */
    public List<SalesRecordState> forecastSales(Integer fromYear, Integer fromMonth, Integer toYear, Integer toMonth, int growthRate) {
        System.out.println("Forecasting Sales from " + fromMonth + "/" + fromYear + " to " + toMonth + "/" + toYear);
        List<SalesRecordState> states = new ArrayList();

        Query q = em.createQuery("SELECT a FROM ConsolidatedMonthlySalesRecordEntity a");
        List<ConsolidatedMonthlySalesRecordEntity> cmsrs = new ArrayList();

        //Getting the requested period consolidated records
        for (Object o : q.getResultList()) {
            ConsolidatedMonthlySalesRecordEntity cmsr = (ConsolidatedMonthlySalesRecordEntity) o;
            Integer yearInt = cmsr.getYearInt();
            Integer monthInt = cmsr.getMonthInt();

            if (!fromYear.equals(toYear)) {
                if (yearInt.intValue() == fromYear.intValue() && monthInt.intValue() >= fromMonth.intValue()) {
                    System.out.println("Processing CMSR" + yearInt + "/" + monthInt);
                    cmsrs.add(cmsr);
                }

                if (yearInt.intValue() == toYear.intValue() && monthInt.intValue() <= toMonth.intValue()) {
                    System.out.println("Processing CMSR" + yearInt + "/" + monthInt);
                    if (!cmsrs.contains(cmsr)) {
                        cmsrs.add(cmsr);
                    }
                }
            } else if (yearInt.intValue() == fromYear.intValue() && monthInt.intValue() >= fromMonth.intValue() && monthInt.intValue() <= toMonth.intValue()) {
                System.out.println("Processing CMSR" + yearInt + "/" + monthInt);
                cmsrs.add(cmsr);
            }

        }
        System.out.println("reached");
        for (ConsolidatedMonthlySalesRecordEntity cmsr : cmsrs) {
            System.out.println("entering protected method...");
            int projectedPromotion = cmsr.getProjectedPromotion();
            System.out.println(cmsr.getId() + "is final? " + cmsr.isForecasted());
            if (!cmsr.isForecasted()) {
                //update forecast figures if forecast is not finalized, else return the stored figures
                forecastSingleMonthlySales(cmsr, growthRate, projectedPromotion);
            }
            SalesRecordState state = new SalesRecordState();
            state.createSalesForecastState(cmsr.getId(), cmsr.getYearInt(), cmsr.getMonthInt(), cmsr.getPreviousSales(), cmsr.getPreviousPromotion(), cmsr.getPreviousBase(), cmsr.getGrowth(), cmsr.getProjectedSales(), cmsr.getProjectedPromotion(), cmsr.getProjectedBase());
            state.setForecasted(cmsr.isForecasted());
            states.add(state);
        }

        return states;

    }

    protected void forecastSingleMonthlySales(ConsolidatedMonthlySalesRecordEntity cmsr, double growthRate, int projectedPromotion) {
        System.out.println("ForecastSingleMonthlySales for " + cmsr.getMonthInt() + "/" + cmsr.getYearInt());
        int projectedGrowth = (int) (cmsr.getPreviousBase() * growthRate / 100);
        int projectedBase = cmsr.getPreviousBase() + projectedGrowth;
        int projectedSales = projectedBase + projectedPromotion;

        cmsr.setGrowth(projectedGrowth);
        cmsr.setProjectedBase(projectedBase);
        cmsr.setProjectedPromotion(projectedPromotion);
        cmsr.setProjectedSales(projectedSales);
        em.merge(cmsr);
        em.flush();

    }

    @Override
    public void finalizeSingleMonthlyForecast(Integer yearInt, Integer monthInt) {
        System.out.println("Finalize Single Monthly Forecast for " + monthInt + "/" + yearInt);

        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(yearInt, monthInt);

        cmsr.setForecasted(true);
        em.merge(cmsr);
        em.flush();
        System.out.println("forecast in demand management bean " + cmsr.isForecasted());
    }

    @Override
    public void deFinalizeSingleMonthlyForecast(Integer yearInt, Integer monthInt) {
        System.out.println("Finalize Single Monthly Forecast");

        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(yearInt, monthInt);

        cmsr.setForecasted(false);
        em.merge(cmsr);
        em.flush();
    }

    @Override
    public SalesRecordState updateSingleMonthlyForecast(Integer yearInt, Integer monthInt, double growthRate, int projectedPromotion) {
        System.out.println("Update Single Monthly Forecast for " + monthInt + "/" + yearInt +", with growth " + growthRate + "& and projectedPromotion = " + projectedPromotion);
        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(yearInt, monthInt);

        if (cmsr == null) {
            throw new NullPointerException("Cannot find the corresponding sales record");
        }
        if (!cmsr.isForecasted()) {
            this.forecastSingleMonthlySales(cmsr, growthRate, projectedPromotion);
        }

        SalesRecordState state = new SalesRecordState();
        state.createSalesForecastState(cmsr.getId(), cmsr.getYearInt(), cmsr.getMonthInt(), cmsr.getPreviousSales(), cmsr.getPreviousPromotion(), cmsr.getPreviousBase(), cmsr.getGrowth(), cmsr.getProjectedSales(), cmsr.getProjectedPromotion(), cmsr.getProjectedBase());
        state.setForecasted(cmsr.isForecasted());
        System.out.println("Session bean: projected sales updated to : " + state.getProjectedSales());
        return state;
    }

    //init functions
    @Override
    public boolean forecastMonthlySales(Long productId, Integer yearInt, Integer monthInt, double growthRate, int projectedPromotion) {
        int projectedMonthlySales;
        MonthlySalesRecordEntity history = this.getMonthlySalesRecord(productId, yearInt - 1, monthInt);
        ProductEntity product = em.find(ProductEntity.class, productId);

        if (product == null) { //Invalid product
            throw new NullPointerException("Invalid product specified");
        }

        if (history == null) { //Historical data does not exist, quit immediately
            //message = "Unable to find historical sales figure.";
            //messages.add//message(//message);
            throw new NullPointerException("Historical figures do not exist.");
        }

        int projectedGrowth = (int) (history.getActualBase() * growthRate / 100);
        int projectedBase = history.getActualBase() + projectedGrowth;
        projectedMonthlySales = projectedBase + projectedPromotion;

        //AnnualSalesRecordEntity currentYear = em.find(AnnualSalesRecordEntity.class, yearInt);
        MonthlySalesRecordEntity current = this.getMonthlySalesRecord(productId, yearInt, monthInt);
        /*
        if (currentYear != null) {
        if (current == null) { //create Current Monthly Sales Record if not exist
        current = new MonthlySalesRecordEntity(currentYear, monthInt);
        em.persist(current);
        Collection<MonthlySalesRecordEntity> msr = currentYear.getMonthlySalesRecords();
        if (msr.add(current)) {
        currentYear.setMonthlySalesRecords(msr);
        //message = "Monthly Sales Record Created.";
        //messages.add//message(//message);
        } else {
        throw new RuntimeException("forecastMonthlySales: Unexpected Exception.");
        }
        } else {
        //message = "Annual and Monthly Sales Record Found.";
        //messages.add//message(//message);
        }
        } else */
        if (current == null) {// create Monthly Sales Record if not yet created
            current = new MonthlySalesRecordEntity(product, yearInt, monthInt);
            em.persist(current);
        }
        //currentYear = new AnnualSalesRecordEntity(yearInt);
        // em.persist(currentYear);
        // em.flush();
            /*
        Collection<MonthlySalesRecordEntity> msr = currentYear.getMonthlySalesRecords();
        if (msr.add(current)) {
        currentYear.setMonthlySalesRecords(msr);
        //message = "Monthly Sales Record Created.";
        //messages.add//message(//message);
        } else {
        throw new RuntimeException("forecastMonthlySales: Unexpected Exception.");
        }
         * 
         */
        //message = "New Annual and Monthly Sales Record Created.";
        //messages.add//message(//message);
        current.setProjectedBase(projectedBase);
        current.setProjectedPromotion(projectedPromotion);
        current.setProjectedSales(projectedMonthlySales);
        em.flush();

        //message = "Sales Projection forecasted.";
        //messages.add//message(//message);

        return true;

    }

    @Override
    public List<SalesOperationPlanState> generateSalesOperationPlanByPeriod(int period, Integer yearInt, List<Object[]> idRatioMap) {

        /*
         * 11 - Quarter 1
         * 12 - Quarter 2
         * 13 - Quarter 3
         * 14 - Quarter 4
         */

        Integer fromMonth, toMonth;

        switch (period) {
            case 11:
                fromMonth = 1;
                toMonth = 3;
                break;
            case 12:
                fromMonth = 4;
                toMonth = 6;
                break;
            case 13:
                fromMonth = 7;
                toMonth = 9;
                break;
            case 14:
                fromMonth = 10;
                toMonth = 12;
                break;
            default:
                fromMonth = 1;
                toMonth = 12;
        }

        Integer lastMonth, lastYear;

        if (fromMonth.intValue() == 1) {
            lastMonth = 12;
            lastYear = yearInt - 1;
        } else {
            lastMonth = fromMonth - 1;
            lastYear = yearInt;
        }

        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(lastYear, lastMonth);

        int projectedBeginningInventory;
        if (cmsr != null) {
            projectedBeginningInventory = cmsr.getEndingInventory();
        } else {
            projectedBeginningInventory = 0;
        }

        List<SalesOperationPlanState> states = this.generateMonthlyPlannedDemands(yearInt, fromMonth, yearInt, toMonth, projectedBeginningInventory, idRatioMap, false, false);

        return states;
    }

    @Override
    public List<SalesOperationPlanState> viewSalesOperationPlanByPeriod(int period, Integer yearInt) {

        /*
         * 11 - Quarter 1
         * 12 - Quarter 2
         * 13 - Quarter 3
         * 14 - Quarter 4
         */

        Integer fromMonth, toMonth;

        switch (period) {
            case 11:
                fromMonth = 1;
                toMonth = 3;
                break;
            case 12:
                fromMonth = 4;
                toMonth = 6;
                break;
            case 13:
                fromMonth = 7;
                toMonth = 9;
                break;
            case 14:
                fromMonth = 10;
                toMonth = 12;
                break;
            default:
                fromMonth = 1;
                toMonth = 12;
        }

        Integer lastMonth, lastYear;

        if (fromMonth.intValue() == 1) {
            lastMonth = 12;
            lastYear = yearInt - 1;
        } else {
            lastMonth = fromMonth - 1;
            lastYear = yearInt;
        }

        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(lastYear, lastMonth);

        int projectedBeginningInventory;
        if (cmsr != null) {
            projectedBeginningInventory = cmsr.getEndingInventory();
        } else {
            projectedBeginningInventory = 0;
        }

        List<SalesOperationPlanState> states = this.generateMonthlyPlannedDemands(yearInt, fromMonth, yearInt, toMonth, projectedBeginningInventory, null, false, true);

        return states;
    }

    @Override
    public List<SalesOperationPlanState> finalizeSalesOperationPlanByPeriod(int period, Integer yearInt, List<Object[]> idRatioMap) {

        /*
         * 11 - Quarter 1
         * 12 - Quarter 2
         * 13 - Quarter 3
         * 14 - Quarter 4
         */

        Integer fromMonth, toMonth;

        switch (period) {
            case 11:
                fromMonth = 1;
                toMonth = 3;
                break;
            case 12:
                fromMonth = 4;
                toMonth = 6;
                break;
            case 13:
                fromMonth = 7;
                toMonth = 9;
                break;
            case 14:
                fromMonth = 10;
                toMonth = 12;
                break;
            default:
                fromMonth = 1;
                toMonth = 12;
        }

        Integer lastMonth, lastYear;

        if (fromMonth.intValue() == 1) {
            lastMonth = 12;
            lastYear = yearInt - 1;
        } else {
            lastMonth = fromMonth - 1;
            lastYear = yearInt;
        }

        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(lastYear, lastMonth);

        int projectedBeginningInventory;
        if (cmsr != null) {
            projectedBeginningInventory = cmsr.getEndingInventory();
        } else {
            projectedBeginningInventory = 0;
        }

        List<SalesOperationPlanState> states = this.generateMonthlyPlannedDemands(yearInt, fromMonth, yearInt, toMonth, projectedBeginningInventory, idRatioMap, true, false);

        return states;
    }

    protected List<SalesOperationPlanState> generateMonthlyPlannedDemands(Integer fromYear, Integer fromMonth, Integer toYear, Integer toMonth, int projectedBeginningInventory, List<Object[]> idRatioMap, boolean finalize, boolean view) {
        System.out.println("Generating Sales and Operation Plans from " + fromMonth + "/" + fromYear + " to " + toMonth + "/" + toYear);

        List<SalesOperationPlanState> states = new ArrayList();

        Query q = em.createQuery("SELECT a FROM ConsolidatedMonthlySalesRecordEntity a");
        List<ConsolidatedMonthlySalesRecordEntity> cmsrs = new ArrayList();

        //Getting the requested period consolidated records
        for (Object o : q.getResultList()) {
            ConsolidatedMonthlySalesRecordEntity cmsr = (ConsolidatedMonthlySalesRecordEntity) o;

            Integer yearInt = cmsr.getYearInt();
            Integer monthInt = cmsr.getMonthInt();

            if (yearInt.intValue() == fromYear.intValue() && monthInt.intValue() >= fromMonth.intValue() && monthInt.intValue() <= toMonth.intValue()) {
                System.out.println("Processing CMSR" + yearInt + "/" + monthInt);
                cmsrs.add(cmsr);
            }
            /*
            if (yearInt.intValue() == toYear.intValue() && monthInt.intValue() <= toMonth.intValue()) {
            System.out.println("Processing CMSR" + yearInt + "/" + monthInt);
            if (!cmsrs.contains(cmsr)) {
            cmsrs.add(cmsr);
            }
            }*/
        }

        int endingInventory = projectedBeginningInventory;
        int beginningInventory = projectedBeginningInventory;
        for (ConsolidatedMonthlySalesRecordEntity cmsr : cmsrs) {
            int topUp = 0;
            if (endingInventory < 100) {
                topUp = 500;
            }
            SalesOperationPlanState state;
            int productionPlan = 0;
            productionPlan = cmsr.getProductionPlan();
            state = this.generateSingleMonthlySalesOperationPlan(cmsr, idRatioMap, topUp, productionPlan, false, finalize, view);
            endingInventory = endingInventory + state.getProductionPlan() - state.getSalesForecast();
            state.setEndingInventory(endingInventory);
            state.setBeginningInventory(beginningInventory);
            beginningInventory = endingInventory;
            states.add(state);
            if (finalize) {
                if(!cmsr.isFinalized()) {
                cmsr.setFinalized(true);
                }
                state.setFinalized(true);
                em.flush();
            }
        }

        return states;

    }

    @Override //update & finalize
    public SalesOperationPlanState updateSingleMonthlySalesOperationPlan(Integer yearInt, Integer monthInt, int productionPlanInput) {
        System.out.println("Updating single MonthlySalesOperationPlan");
        ConsolidatedMonthlySalesRecordEntity cmsr = this.getConsolidatedMonthlySalesRecord(yearInt, monthInt);

        List<Object[]> idRatioMap = new ArrayList();
        idRatioMap = (List<Object[]>) cmsr.getCompositions();
        SalesOperationPlanState state;
        if (!idRatioMap.isEmpty()) {
            state = this.generateSingleMonthlySalesOperationPlan(cmsr, idRatioMap, 0, productionPlanInput, true, false, false);
            System.out.println("Sales Operation Plan updated for " + cmsr.toString());
            return state;
        } else {
            throw new IllegalStateException("Unable to update sales operation plan, it must first be created");
        }

    }

    protected SalesOperationPlanState generateSingleMonthlySalesOperationPlan(ConsolidatedMonthlySalesRecordEntity cmsr, List<Object[]> idRatioMap, int topUp, int productionPlanInput, boolean update, boolean finalize, boolean view) {
        if (cmsr.isFinalized() || view || finalize) {

            Integer yearInt = cmsr.getYearInt();
            Integer monthInt = cmsr.getMonthInt();
            int salesForecast = cmsr.getProjectedSales();
            int productionPlan = cmsr.getProductionPlan();
            int nWorkdays = cmsr.getnWorkdays();
            int capacity = cmsr.getCapacity();
            int utilization = cmsr.getUtilization();
            List<PlannedMonthlyDemandEntity> pmds = new ArrayList<PlannedMonthlyDemandEntity>();
            pmds = (List<PlannedMonthlyDemandEntity>) cmsr.getPlannedMonthlyDemands();

            List<SOPBreakdownState> states = new ArrayList();
            for (PlannedMonthlyDemandEntity pmd : pmds) {
                SOPBreakdownState state = new SOPBreakdownState(pmd.getProduct().getId(), pmd.getProduct().getName(), (int) pmd.getRatio(), pmd.getMonthlyDemand());
                states.add(state);
            }
            for (SOPBreakdownState composition : states) {
                composition.setQuantity((int) (composition.getRatio() * productionPlan / 100));
            }

            SalesOperationPlanState state = new SalesOperationPlanState(yearInt, monthInt, salesForecast, productionPlan, nWorkdays, capacity, utilization);
            state.setFinalized(cmsr.isFinalized());
            if (finalize) {
                cmsr.setFinalized(true);
                state.setFinalized(cmsr.isFinalized());
            }
            
            System.out.println("Capacity = " + capacity + ", #workdays = " + nWorkdays);
            System.out.println("1) Final View: " + state.isFinalized());
            state.setBreakdowns(states);

            return state;
        }

        List<SOPBreakdownState> compositions = new ArrayList(); // [id, name, ratio, quantity]
        Integer yearInt = cmsr.getYearInt();
        Integer monthInt = cmsr.getMonthInt();

        List<PlannedMonthlyDemandEntity> pmds = new ArrayList();
        pmds = (List<PlannedMonthlyDemandEntity>) cmsr.getPlannedMonthlyDemands();

        int nWorkdays = this.getWorkingDaysNumInMonth(yearInt, monthInt);
        int salesForecast = cmsr.getProjectedSales();
        int dailyCapacity = 2 * 8 * 50; // assume that the production of classic bars predominates
        int capacity = nWorkdays * dailyCapacity;
        int productionPlan = salesForecast;
        int utilization;
        if (!update) {
            utilization = (int) productionPlan * 100 / capacity;
            if (utilization > 90) { // utilization is capped at 90
                productionPlan = (int) 0.9 * capacity;
                utilization = 90;
                for (SOPBreakdownState composition : compositions) {
                    composition.setQuantity((int) (composition.getRatio() * productionPlan / 100));
                }
            }
            if (topUp > 0) { // activated when inventory falls below allowable minimum
                productionPlan = salesForecast + topUp;
                utilization = productionPlan * 100 / capacity;
                if (utilization > 137) {
                    /*
                     * maximum allowable overcloak (8 normal hours + 4 overtime hours)
                     * 
                     */
                    productionPlan = (int) 1.375 * capacity;
                    utilization = 137;
                    for (SOPBreakdownState composition : compositions) {
                        composition.setQuantity((int) (composition.getRatio() * productionPlan / 100));
                    }
                    System.out.println("Capacity exceeded");
                }
                //sending message to all production executives regarding overcloak plan for the month
            }
        } else {
            productionPlan = productionPlanInput;
            utilization = (int) productionPlan * 100 / capacity;
            if (utilization > 137 || utilization < 0) {
                /*
                 * maximum allowable overcloak (8 normal hours + 4 overtime hours)
                 * 
                 */
                productionPlan = (int) 1.375 * capacity;
                utilization = 137;
                System.out.println("Capacity exceeded");

                for (SOPBreakdownState composition : compositions) {
                    composition.setQuantity((int) (composition.getRatio() * productionPlan / 100));
                }
            }
            System.out.println("Updated production plan = " + productionPlan + ", updated utilization = " + utilization + "%");
        }

        // getting & updating composition ratio
        for (Object[] o : idRatioMap) {
            Long productId = (Long) o[0];
            int ratio = (Integer) o[1];
            int demand = (int) (ratio * salesForecast / 100 + 0.5);
            System.out.print("Ratio is " + ratio + ", demand = " + demand);
            PlannedMonthlyDemandEntity pmd = this.getPlannedMonthlyDemand(productId, yearInt, monthInt);
            ProductEntity product = em.find(ProductEntity.class, productId);


            if (pmd == null) {
                pmd = new PlannedMonthlyDemandEntity(product, yearInt, monthInt);
                em.persist(pmd);
                em.flush();
            }

            pmd.setConsolidatedMonthlySalesRecordEntity(cmsr);

            pmd.setMonthlyDemand(demand);

            pmd.setRatio(ratio);


            if (!pmds.contains(pmd)) {
                pmds.add(pmd);
            }
            SOPBreakdownState state = new SOPBreakdownState(product.getId(), product.getName(), ratio, demand);
            compositions.add(state);

            em.flush();
        }
        SalesOperationPlanState state = new SalesOperationPlanState(yearInt, monthInt, salesForecast, productionPlan, nWorkdays, capacity, utilization);
        cmsr.setCompositions(idRatioMap);
        cmsr.setPlannedMonthlyDemands(pmds);
        cmsr.setProductionPlan(productionPlan);
        System.out.println("2nd check: Updated production plan = " + productionPlan + ", updated utilization = " + utilization + "%");
        cmsr.setUtilization(utilization);
        cmsr.setCapacity(capacity);
        cmsr.setnWorkdays(nWorkdays);
        if (update || finalize) {
            cmsr.setFinalized(true);
        }
        em.flush();
        state.setFinalized(cmsr.isFinalized());
        System.out.println("2) Final view: " + state.isFinalized());
        state.setBreakdowns(compositions);

        return state;
    }

    /*
     * Public method that:
     *  1. aggregate the monthly projected sales and 
     *  2. update corresponding entries in the Annual Sales Record
     */
    /*
    @Override
    public boolean aggregateMonthlyProjectedSales(Integer yearInt) {
    
    AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
    int projectedSales = 0;
    int projectedPromotion = 0;
    int projectedBase = 0;
    
    if (asr == null) { // No Annual Sales Record found
    throw new NullPointerException("aggregateMonthlyProjectedSales: No sales record for the specified year");
    }
    
    for (MonthlySalesRecordEntity msr : asr.getMonthlySalesRecords()) {
    projectedSales += msr.getProjectedSales();
    projectedBase += msr.getProjectedBase();
    projectedPromotion += msr.getProjectedPromotion();
    }
    
    asr.setProjectedBase(projectedBase);
    asr.setProjectedPromotion(projectedPromotion);
    asr.setProjectedSales(projectedSales);
    em.flush();
    
    //message = "aggregateMonthlyProjectedSales: Aggregate successful.";
    //messages.add//message(//message);
    return true;
    
    }
    
    /*
     * Public method that:
     *  1. aggregates actual monthly sales figures and
     *  2. updates corresponding entries in the Annual Sales Record
     */
    /*
    @Override
    public boolean aggregateMonthlyActualSales(Integer yearInt) {
    
    AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
    int actualSales = 0;
    int actualPromotion = 0;
    int actualBase = 0;
    
    if (asr == null) {
    throw new NullPointerException("aggregateMonthlyActualSales: No sales record for the specified year");
    }
    
    for (MonthlySalesRecordEntity msr : asr.getMonthlySalesRecords()) {
    actualSales += msr.getActualSales();
    actualBase += msr.getActualBase();
    actualPromotion += msr.getActualPromotion();
    }
    
    asr.setActualBase(actualBase);
    asr.setActualPromotion(actualPromotion);
    asr.setActualSales(actualSales);
    em.flush();
    //message = "aggregateMonthlyActualSales: Aggregate successful.";
    //messages.add//message(//message);
    return true;
    
    }
    
    /*
     * Public method that check whether the aggregated projected sales figures for that year tallies with the annual figures
     */
    /*
    @Override
    public boolean tallyProjectedSalesInYear(Integer yearInt) {
    AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
    int projectedSales = 0;
    int projectedPromotion = 0;
    int projectedBase = 0;
    
    if (asr == null) {
    throw new NullPointerException("aggregateMonthlyProjectedSales: No sales record for the specified year");
    }
    
    for (MonthlySalesRecordEntity msr : asr.getMonthlySalesRecords()) {
    projectedSales += msr.getProjectedSales();
    projectedBase += msr.getProjectedBase();
    projectedPromotion += msr.getProjectedPromotion();
    }
    
    boolean salesTally = projectedSales == asr.getProjectedSales();
    boolean promotionTally = projectedPromotion == asr.getProjectedPromotion();
    boolean baseTally = projectedBase == asr.getProjectedBase();
    
    if (salesTally && promotionTally && baseTally) {
    //message = "Projected Sales Figures in Annual and Monthly sales records match.";
    //messages.add//message(//message);
    return true;
    } else {
    //message = "Projected Sales Figures in Annual and Monthly sales do not match.";
    if (!salesTally) {
    //message = "Projected Sales do not tally";
    //messages.add//message(//message);
    }
    if (!promotionTally) {
    //message = "Projected Promotions do not tally";
    //messages.add//message(//message);
    }
    if (!baseTally) {
    //message = "Projected Bases do not tally";
    //messages.add//message(//message);
    }
    return false;
    }
    }
    
    /*
     * Public method that check whether the aggregated actual sales figures for that year tallies with the annual figures
     */
    /*
    @Override
    public boolean tallyActualSalesInYear(Integer yearInt) {
    AnnualSalesRecordEntity asr = em.find(AnnualSalesRecordEntity.class, yearInt);
    int actualSales = 0;
    int actualPromotion = 0;
    int actualBase = 0;
    
    if (asr == null) {
    throw new NullPointerException("aggregateMonthlyActualSales: No sales record for the specified year");
    }
    
    for (MonthlySalesRecordEntity msr : asr.getMonthlySalesRecords()) {
    actualSales += msr.getActualSales();
    actualBase += msr.getActualBase();
    actualPromotion += msr.getActualPromotion();
    }
    
    boolean salesTally = actualSales == asr.getActualSales();
    boolean promotionTally = actualPromotion == asr.getActualPromotion();
    boolean baseTally = actualBase == asr.getActualBase();
    
    if (salesTally && promotionTally && baseTally) {
    //message = "Actual Sales Figures in Annual and Monthly sales records match.";
    //messages.add//message(//message);
    return true;
    } else {
    //message = "Actual Sales Figures in Annual and Monthly sales do not match.";
    if (!salesTally) {
    //message = "Actual Sales do not tally";
    //messages.add//message(//message);
    }
    if (!promotionTally) {
    //message = "Actual Promotions do not tally";
    //messages.add//message(//message);
    }
    if (!baseTally) {
    //message = "Actual Bases do not tally";
    //messages.add//message(//message);
    }
    return false;
    }
    }
    
    
    /*
     * Public method to convert projected month sales to planned monthlly demand.
     */
    @Override
    public boolean convertMonthlyProjectedSalesToPlannedDemand(Long productId, Integer yearInt, Integer monthInt) {// {
        MonthlySalesRecordEntity msr = this.getMonthlySalesRecord(productId, yearInt, monthInt);
        ProductEntity product = em.find(ProductEntity.class, productId);


        if (product
                == null) {
            throw new NullPointerException("Product not found");
        }

        if (msr
                == null) {
            throw new NullPointerException("cannot find Monthly Sales Record to be converted.");
        }


        if (msr.isConverted()) {
            //throw new IllegalStateException("MonthlySalesRecord has been converted previously with ID: " + id);
            System.out.println("Monthly Sales Record has been converted previously: " + msr.getPlannedMonthlyDemand().toString());
            return false;
        }
        /*{
        if (!force) {
        throw new IllegalStateException("This Monthly Sales Record has already been converted");
        //message = "MonthlySalesRecord has been converted to individual monthly planned demand previously.";
        //messages.add//message(//message);
        } else 
        { //if force breakdown, drop previously broken down Planned Monthly Demand
        Collection<PlannedMonthlyDemandEntity> pmds = msr.getPlannedMonthlyDemands();
        for (PlannedMonthlyDemandEntity pmd : pmds) {
        if (pmds.remove(pmd)) {
        msr.setPlannedMonthlyDemands(pmds);
        } else {
        throw new RuntimeException("convertMonthlyProjectedSalesToPlannedDemand: Unexpected Exception.");
        }
        em.detach(pmd);
        }
        
        em.flush();
        }
        }
        int i = 0;
        PlannedMonthlyDemandEntity[] pmd = new PlannedMonthlyDemandEntity[10];
        Collection<PlannedMonthlyDemandEntity> pmds = msr.getPlannedMonthlyDemands();
        
        for (MonthlySalesRecordProductBreakdownEntity msrpb : msr.getMonthlySalesRecordProductBreakdownEntities()) {
        pmd[i] = new PlannedMonthlyDemandEntity(msrpb.getProduct(), yearInt, monthInt);
        pmd[i].setMonthlySalesRecordEntity(msr);
        pmd[i].setMonthlyDemand(msrpb.getAmount());
        em.persist(pmd[i]);
        if (pmds.add(pmd[i])) {
        msr.setPlannedMonthlyDemands(pmds);
        } else {
        throw new RuntimeException("convertMonthlyProjectedSalesToPlannedDemand: Unexpected Exception.");
        }
        em.flush();
        
        //message = "New Planned Monthly Demand created for Product [Name: " + pmd[i].getProduct().getName() + "]";
        //messages.add//message(//message);
        i++;
        }
         */
        PlannedMonthlyDemandEntity pmd = this.getPlannedMonthlyDemand(productId, yearInt, monthInt);

        if (pmd
                != null) {
            System.out.println("Planned Monthly Demands already exist: " + pmd.toString());
            return false;
        }
        pmd = new PlannedMonthlyDemandEntity(product, yearInt, monthInt);

        em.persist(pmd);

        pmd.setMonthlySalesRecordEntity(msr);

        pmd.setMonthlyDemand(msr.getProjectedSales());
        msr.setPlannedMonthlyDemand(pmd);

        msr.setConverted(
                true);

        em.flush();


        return true;
    }

    /*
     * Public method to create monthly planned demand manually
     */
    @Override
    public boolean createMonthlyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, int demand) {

        MonthlySalesRecordEntity msr = this.getMonthlySalesRecord(productId, yearInt, monthInt);
        ProductEntity product = em.find(ProductEntity.class, productId);



        if (msr
                == null) {
            //message = "Cannot find corresponding Monthly Sales Record for Planned Monthly Demand creation";
            //messages.add//message(//message);
            throw new NullPointerException("No corresponding Monthly Sales Record for Planned Demand creation.");
        }

        if (product
                == null) {
            //message = "Cannot find corresponding Product for Planned Monthly Demand creation";
            //messages.add//message(//message);
            throw new NullPointerException("Invalid product specified");
        }

        if (product
                != msr.getProduct()) {
            throw new IllegalStateException("The product specified does not match the corresponding Monthly Sales Record");
        }
        /*
        ArrayList<ProductEntity> products = new ArrayList<ProductEntity>();
        
        for (PlannedMonthlyDemandEntity pmd : msr.getPlannedMonthlyDemands()) {
        products.add(pmd.getProduct());
        }
        
        
        
        
        if (products.contains(product)) {
        //message = "Planned Monthly Demand already exist for the corresponding Product and Monthly Sales Record";
        //messages.add//message(//message);
        return false;
        }
        
         */
        PlannedMonthlyDemandEntity pmd = new PlannedMonthlyDemandEntity(product, yearInt, monthInt);

        em.persist(pmd);

        pmd.setMonthlyDemand(demand);

        pmd.setMonthlySalesRecordEntity(msr);

        msr.setPlannedMonthlyDemand(pmd);

        msr.setConverted(
                true);

        /*
        Collection<PlannedMonthlyDemandEntity> pmds = msr.getPlannedMonthlyDemands();
        
        
        if (pmds.add(pmd)) {
        msr.setPlannedMonthlyDemands(pmds);
        } else {
        throw new RuntimeException("createMonthlyPlannedDemand: Unexpected Exception.");
        }
         */
        em.flush();
        //message = "Planned Monthly Demand created.";
        //messages.add//message(//message);


        return true;
    }

    @Override
    public boolean updateMonthlyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, int demand) {

        PlannedMonthlyDemandEntity pmd = this.getPlannedMonthlyDemand(productId, yearInt, monthInt);

        pmd.setMonthlyDemand(demand);
        em.flush();
        //message = "Planned Monthly Demand updated.";
        //messages.add//message(//message);
        return true;
    }

    @Override
    public boolean deleteMonthlyPlannedDemand(Long productId, Integer yearInt, Integer monthInt) {

        PlannedMonthlyDemandEntity pmd = this.getPlannedMonthlyDemand(productId, yearInt, monthInt);

        MonthlySalesRecordEntity msr = pmd.getMonthlySalesRecordEntity();

        /*
        Collection<PlannedMonthlyDemandEntity> pmds = msr.getPlannedMonthlyDemands();
        if (!pmds.remove(pmd)) {
        throw new RuntimeException("deleteMonthlyPlannedDemand: Unexpected Exception.");
        }
        msr.setPlannedMonthlyDemands(pmds);
         *
         */
        List<PlannedWeeklyDemandEntity> pwds = new ArrayList<PlannedWeeklyDemandEntity>();
        pwds = (List<PlannedWeeklyDemandEntity>) pmd.getPlannedWeeklyDemandEntities();
        List<PlannedDailyDemandEntity> pdds = new ArrayList<PlannedDailyDemandEntity>();

        for (PlannedWeeklyDemandEntity pwd : pwds) {
            pwd.setPlannedMonthlyDemand(null);

            pdds = (List<PlannedDailyDemandEntity>) pwd.getPlannedDailyDemandEntities();

            for (PlannedDailyDemandEntity pdd : pdds) {
                pdd.setPlannedWeeklyDemand(null);
                em.remove(pdd);
            }

            pdds.clear();
            pwd.setPlannedDailyDemandEntities(pdds);
            em.remove(pwd);
        }

        pwds.clear();
        pmd.setPlannedWeeklyDemandEntities(pwds);

        msr.setPlannedMonthlyDemand(null);
        msr.setConverted(false);
        em.remove(pmd);
        em.flush();
        //message = "Planned Monthly Demand deleted";
        //messages.add//message(//message);
        return true;
    }

    @Override
    public boolean createWeeklyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth, int demand) {

        ProductEntity product = em.find(ProductEntity.class, productId);
        PlannedMonthlyDemandEntity pmd = this.getPlannedMonthlyDemand(productId, yearInt, monthInt);

        if (product
                == null) {
            //message = "createWeeklyPlannedDemand failed. No such product.";
            //messages.add//message(//message);
            throw new NullPointerException("The product specified does not exist.");
        }

        if (pmd
                == null) {
            //message = "createWeeklyPlannedDemand failed. No planned monthly demand found.";
            //messages.add//message(//message);
            throw new NullPointerException("The corresponding planned monthly demand does not exist.");
        }
        PlannedWeeklyDemandEntity pwd = new PlannedWeeklyDemandEntity(product, yearInt, monthInt, weekOfMonth);

        em.persist(pwd);

        pwd.setWeeklyDemand(demand);

        pwd.setPlannedMonthlyDemand(pmd);
        Collection<PlannedWeeklyDemandEntity> pwds = pmd.getPlannedWeeklyDemandEntities();


        if (pwds.add(pwd)) {
            pmd.setPlannedWeeklyDemandEntities(pwds);
        } else {
            throw new RuntimeException("createWeeklyPlannedDemand. Unexpected Expection.");
        }

        em.flush();


        return true;

    }

    @Override
    public boolean updateWeeklyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth, int demand) {
        PlannedWeeklyDemandEntity pwd = this.getPlannedWeeklyDemand(productId, yearInt, monthInt, weekOfMonth);

        pwd.setWeeklyDemand(demand);
        em.flush();
        //message = "Planned Weekly Demand updated.";
        //messages.add//message(//message);
        return true;

    }

    @Override
    public boolean deleteWeeklyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekofMonth) {
        PlannedWeeklyDemandEntity pwd = this.getPlannedWeeklyDemand(productId, yearInt, monthInt, weekofMonth);

        PlannedMonthlyDemandEntity pmd = pwd.getPlannedMonthlyDemand();

        Collection<PlannedWeeklyDemandEntity> pwds = pmd.getPlannedWeeklyDemandEntities();


        List<PlannedDailyDemandEntity> pdds = new ArrayList();
        pdds = (List<PlannedDailyDemandEntity>) pwd.getPlannedDailyDemandEntities();

        for (PlannedDailyDemandEntity pdd : pdds) {
            pdd.setPlannedWeeklyDemand(null);
            em.remove(pdd);
        }
        pdds.clear();

        pwd.setPlannedDailyDemandEntities(pdds);
        pmd.setPlannedWeeklyDemandEntities(pwds);
        pwds.remove(pwd);
        pwd.setPlannedMonthlyDemand(null);
        em.remove(pwd);
        em.flush();
        //message = "Planned Weekly Demand deleted";
        //messages.add//message(//message);
        return true;
    }

    @Override
    public boolean createDailyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth, Integer dayOfMonth, int demand) {

        ProductEntity product = em.find(ProductEntity.class, productId);
        PlannedWeeklyDemandEntity pwd = this.getPlannedWeeklyDemand(productId, yearInt, monthInt, weekOfMonth);

        if (pwd == null) {
            //message = "createDailyPlannedDemand failed. No planned weekly demand found.";
            //messages.add//message(//message);
            return false;
        }

        if (product == null) {
            //message = "createDailyPlannedDemand failed. No such product.";
            //messages.add//message(//message);
            return false;
        }
        PlannedDailyDemandEntity pdd = new PlannedDailyDemandEntity(product, yearInt, monthInt, weekOfMonth, dayOfMonth);

        em.persist(pdd);

        pdd.setDailyDemand(demand);

        pdd.setReservedQty(demand);

        pdd.setPlannedWeeklyDemand(pwd);
        Collection<PlannedDailyDemandEntity> pdds = pwd.getPlannedDailyDemandEntities();


        if (pdds.add(pdd)) {
            pwd.setPlannedDailyDemandEntities(pdds);
        } else {
            throw new RuntimeException("createDailyPlannedDemand. Unexpected Expection.");
        }

        em.flush();


        return true;

    }

    @Override
    public boolean updateDailyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth, Integer dayOfMonth, int demand) {
        PlannedDailyDemandEntity pwd = this.getPlannedDailyDemand(productId, yearInt, monthInt, weekOfMonth, dayOfMonth);

        pwd.setDailyDemand(demand);
        em.flush();
        //message = "Planned Daily Demand updated.";
        //messages.add//message(//message);
        return true;
    }

    @Override
    public boolean deleteDailyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth, Integer dayOfMonth) {
        PlannedDailyDemandEntity pdd = this.getPlannedDailyDemand(productId, yearInt, monthInt, weekOfMonth, dayOfMonth);

        PlannedWeeklyDemandEntity pwd = pdd.getPlannedWeeklyDemand();

        Collection<PlannedDailyDemandEntity> pdds = pwd.getPlannedDailyDemandEntities();
        pdds.remove(pdd);
        pdd.setPlannedWeeklyDemand(null);
        pwd.setPlannedDailyDemandEntities(pdds);
        em.remove(pdd);
        em.flush();
        //message = "Planned Daily Demand deleted";
        //messages.add//message(//message);
        return true;
    }

    @Override
    public boolean createUnplannedDailyDemand(BackOrderEntity bo, PlannedDailyDemandEntity pdd, int demand) {

        UnplannedDailyDemandEntity udd = new UnplannedDailyDemandEntity(pdd, demand);
        em.persist(udd);
        List<UnplannedDailyDemandEntity> udds = new ArrayList<UnplannedDailyDemandEntity>();
        //updating planned daily demand
        udds = (List<UnplannedDailyDemandEntity>) pdd.getUnplannedDailyDemandEntitys();
        udds.add(udd);
        pdd.setUnplannedDailyDemandEntitys(udds);
        udd.setPlannedDailyDemandEntity(pdd);
        //updating reservation
        ReservationEntity reservation = new ReservationEntity();
        reservation.reserveUnplannedDemand(pdd.getProduct().getId(), demand, bo, udd);
        em.persist(reservation);
        List<ReservationEntity> reservations = new ArrayList();
        reservations = bo.getReservationEntities();
        reservations.add(reservation);
        bo.setReservationEntities(reservations);
        reservation.setUnplannedDailyDemand(udd);
        em.flush();

        return true;
    }

    @Override
    public boolean deleteUnplannedDailyDemand(Long id) { //logical deletion only

        UnplannedDailyDemandEntity udd = em.find(UnplannedDailyDemandEntity.class, id);
        int quantity = udd.getQuantity();

        if (udd == null) {
            return false;
        }

        //updating planned daily demand
        PlannedDailyDemandEntity pdd = udd.getPlannedDailyDemandEntity();
        List<UnplannedDailyDemandEntity> udds = new ArrayList<UnplannedDailyDemandEntity>();
        udds = (List<UnplannedDailyDemandEntity>) pdd.getUnplannedDailyDemandEntitys();
        udds.remove(udd);
        pdd.setUnplannedDailyDemandEntitys(udds);

        if (udd.isCommitted()) {
            pdd.setAdjustedQty(pdd.getAdjustedQty() - quantity);
        }

        //updating reservation
        ReservationEntity reservation = udd.getReservation();
        udd.setPlannedDailyDemandEntity(null);
        udd.setReservation(null);
        udd.setDeleteFlag(true);
        reservation.setUnplannedDailyDemand(null);
        reservation.setVoided(true);

        em.flush();

        return true;
    }

    @Override
    public void commitUnplannedDailyDemand(Long id) {
        UnplannedDailyDemandEntity udd = em.find(UnplannedDailyDemandEntity.class, id);
        PlannedDailyDemandEntity pdd = udd.getPlannedDailyDemandEntity();

        pdd.setAdjustedQty(pdd.getAdjustedQty() + udd.getQuantity());
        udd.setCommitted(true);
        em.flush();
    }

    @Override
    public void updateUnplannedDailyDemand(Long id, int quantity) {
        UnplannedDailyDemandEntity udd = em.find(UnplannedDailyDemandEntity.class, id);
        udd.setQuantity(quantity);

        em.flush();
    }

    /*
     * Public method to set the Product Composition of a Projected Sales
     *
    @Override
    public boolean setProjectionCompositionPerProduct(MonthlySalesRecordEntity monthlySalesRecord, ProductEntity product, double percentage) {
    double amountDouble = percentage / 100 * monthlySalesRecord.getProjectedSales();
    int amount = (int) (amountDouble + 0.5); // casting double to integer will leave only the round part without actual rounding, this is a workaround to ensure correct rounding.
    MonthlySalesRecordProductBreakdownEntity msrpb = new MonthlySalesRecordProductBreakdownEntity(monthlySalesRecord, product, percentage, amount);
    em.persist(msrpb);
    em.flush();
    return true;
    }
     * 
     */
    /*
     * Public method to tally the aggregated product composition with the projected sales of a particular month
     */
    /*
    @Override
    public boolean tallyMonthlyProjectionComposition(Integer yearInt, Integer monthInt) {
    
    MonthlySalesRecordEntity msr = this.getMonthlySalesRecordFromYearMonth(yearInt, monthInt);
    
    if (msr == null) {
    throw new NullPointerException("cannot find Monthly Sales Record to be tallied.");
    }
    
    int diff = msr.getProjectedSales();
    for (MonthlySalesRecordProductBreakdownEntity msrpb : msr.getMonthlySalesRecordProductBreakdownEntities()) {
    diff -= msrpb.getAmount();
    }
    
    if (diff == 0) {
    //message = "Projected Product Compositions tally with Projected Sales";
    //messages.add//message(//message);
    return true;
    } else {
    //message = "Projected Product Compositions do not tally with Projected Sales";
    //messages.add//message(//message);
    //message = "Count Difference = " + diff + " compared to the Projected Sales for the month";
    //messages.add//message(//message);
    return false;
    }
    }
    
     */
    @Override
    public SalesRecordState aggregateAnnualForecast(Integer yearInt) {
        Query q = em.createQuery("SELECT m FROM MonthlySalesRecordEntity m WHERE m.yearInt = " + yearInt.toString() + " ORDER BY m.monthInt");

        int projectedSales = 0;
        int projectedPromotion = 0;
        int projectedBase = 0;
        ArrayList<Object[]> msrs = new ArrayList();

        for (Object o : q.getResultList()) {
            MonthlySalesRecordEntity msr = (MonthlySalesRecordEntity) o;
            Object details[] = new Object[6];
            projectedSales += msr.getProjectedSales();
            projectedPromotion += msr.getProjectedPromotion();
            projectedBase += msr.getProjectedBase();

            details[0] = msr.getMonthInt();
            details[1] = msr.getProduct().getId();
            details[2] = msr.getProjectedSales();
            details[3] = msr.getProjectedPromotion();
            details[4] = msr.getProjectedBase();
            details[5] = msr.isConverted();
            for (Object detail : details) {
                System.out.println("detail: " + detail);
            }
            msrs.add(details);
        }

        SalesRecordState state = new SalesRecordState(yearInt, projectedSales, projectedPromotion, projectedBase, msrs);

        return state;
    }

    @Override
    public SalesRecordState aggregateAnnualSales(Integer yearInt) {
        Query q = em.createQuery("SELECT m FROM MonthlySalesRecordEntity m WHERE m.yearInt = " + yearInt.toString() + " ORDER BY m.monthInt");

        int actualSales = 0;
        int actualPromotion = 0;
        int actualBase = 0;
        ArrayList<Object[]> msrs = new ArrayList();

        for (Object o : q.getResultList()) {
            MonthlySalesRecordEntity msr = (MonthlySalesRecordEntity) o;
            Object details[] = new Object[5];
            actualSales += msr.getActualSales();
            actualPromotion += msr.getActualPromotion();
            actualBase += msr.getActualBase();

            details[0] = msr.getMonthInt();
            details[1] = msr.getProduct().getId();
            details[2] = msr.getActualSales();
            details[3] = msr.getActualPromotion();
            details[4] = msr.getActualBase();
            msrs.add(details);
        }

        SalesRecordState state = new SalesRecordState(yearInt, actualSales, actualPromotion, actualBase, msrs);

        return state;
    }

    @Override
    public List<SalesRecordState> viewMonthlySalesRecords(String constraint) {
        String query = "SELECT m FROM MonthlySalesRecordEntity m ";
        if (constraint != null) {
            query += constraint;
        }

        Query q = em.createQuery(query);
        List stateList = new ArrayList();

        for (Object o : q.getResultList()) {
            MonthlySalesRecordEntity msr = (MonthlySalesRecordEntity) o;
            SalesRecordState msrState = new SalesRecordState(msr.getId(), msr.getYearInt(), msr.getMonthInt(), msr.getActualSales(), msr.getActualPromotion(),
                    msr.getActualBase(), msr.getProjectedSales(), msr.getProjectedPromotion(), msr.getProjectedBase(), msr.getProduct(), msr.getPlannedMonthlyDemand(), msr.isConverted());
            stateList.add(msrState);
        }

        return stateList;

    }

    public List<SalesRecordState> viewMonthlySalesRecordsByProduct(Long id) {
        String query = "SELECT m FROM MonthlySalesRecordEntity m";
        /*if (constraint != null) {
        query += constraint;
        }*/

        Query q = em.createQuery(query);
        List stateList = new ArrayList();

        for (Object o : q.getResultList()) {
            MonthlySalesRecordEntity msr = (MonthlySalesRecordEntity) o;
            if (msr.getProduct().getId().equals(id)) {
                SalesRecordState msrState = new SalesRecordState(msr.getId(), msr.getYearInt(), msr.getMonthInt(), msr.getActualSales(), msr.getActualPromotion(),
                        msr.getActualBase(), msr.getProjectedSales(), msr.getProjectedPromotion(), msr.getProjectedBase(), msr.getProduct(), msr.getPlannedMonthlyDemand(), msr.isConverted());
                stateList.add(msrState);
            }
        }

        return stateList;
    }

    @Override // also a view method
    public List<PlannedDemandState> generateWeeklyPlannedDemands(Long productId, Integer yearInt, Integer monthInt) {
        PlannedMonthlyDemandEntity pmd = this.getPlannedMonthlyDemand(productId, yearInt, monthInt);

        if (pmd == null) {
            throw new NullPointerException("The corresponding planned monthly demand does not exist.");
        }

        if (pmd.isDecomposed()) {
            List<PlannedWeeklyDemandEntity> pwds = new ArrayList<PlannedWeeklyDemandEntity>();
            pwds = (List<PlannedWeeklyDemandEntity>) pmd.getPlannedWeeklyDemandEntities();
            List<PlannedDemandState> states = new ArrayList();
            for (PlannedWeeklyDemandEntity pwd : pwds) {
                PlannedDemandState state = new PlannedDemandState();
                state.createWeeklyPlannedDemandState(pwd.getId(), yearInt, monthInt, pwd.getWeekOfMonth(), productId, pmd.getMonthlyDemand(), pwd.getWeeklyDemand(), pwd.getnWorkdays(), pmd.getnWorkdays());

                Integer dayInt = pwd.getDayOfMonth();
                Calendar cal = Calendar.getInstance();
                cal.set(Calendar.YEAR, yearInt);
                cal.set(Calendar.MONTH, monthInt - 1);
                cal.set(Calendar.DATE, dayInt);
                Date execDate = cal.getTime();
                state.setExecDate(execDate);

                if (pwd.getnWorkdays() != 0) {
                    states.add(state);
                }
            }
            return states;
        }

        double monthlyDemand = (double) pmd.getMonthlyDemand();
        double nWorkdays = (double) this.getWorkingDaysNumInMonth(yearInt, monthInt);
        System.out.println("monthlyDemand for " + pmd.getId() + ": " + monthlyDemand + ", no of working days: " + nWorkdays);
        int nWorkInWeek[] = this.getWorkingDaysInWeeksInMonth(yearInt, monthInt);
        List<PlannedWeeklyDemandEntity> pwds = new ArrayList<PlannedWeeklyDemandEntity>();
        pwds = (List<PlannedWeeklyDemandEntity>) pmd.getPlannedWeeklyDemandEntities();
        PlannedWeeklyDemandEntity pwd;
        int i = 0;

        List<PlannedDemandState> states = new ArrayList();

        boolean toCreate = true;

        if (!pwds.isEmpty()) {
            toCreate = false;
        }
        System.out.println("To Create: " + toCreate);
        for (PlannedWeeklyDemandEntity k : pwds) {
            System.out.println(k.toString());
        }

        for (int n : nWorkInWeek) {
            if (toCreate) {
                pwd = new PlannedWeeklyDemandEntity(pmd.getProduct(), yearInt, monthInt, (i + 1));
                em.persist(pwd);
            } else {
                pwd = this.getPlannedWeeklyDemand(productId, yearInt, monthInt, i + 1);
            }
            double nDouble = (double) n;
            int weeklyDemand = (int) ((nDouble * monthlyDemand / nWorkdays) + 0.5);
            System.out.println("weeklyDemand for " + pwd.getId() + ": " + weeklyDemand + ", working days in week: " + n + ", monthlyDemand: " + monthlyDemand + ", total no of working days: " + nWorkdays);
            pwd.setnWorkdays(n);
            pwd.setWeeklyDemand(weeklyDemand);
            pwd.setPlannedMonthlyDemand(pmd);
            pwds.add(pwd);
            pmd.setPlannedWeeklyDemandEntities(pwds);
            PlannedDemandState state = new PlannedDemandState();
            state.createWeeklyPlannedDemandState(pwd.getId(), yearInt, monthInt, pwd.getWeekOfMonth(), productId, (int) monthlyDemand, weeklyDemand, n, (int) nWorkdays);

            Integer dayInt = pwd.getDayOfMonth();
            Calendar cal = Calendar.getInstance();
            cal.set(Calendar.YEAR, yearInt);
            cal.set(Calendar.MONTH, monthInt - 1);
            cal.set(Calendar.DATE, dayInt);
            Date execDate = cal.getTime();
            state.setExecDate(execDate);

            if (n != 0) {
                states.add(state);
            }
            em.flush();
            pwd = null;

            i++;
        }
        pmd.setnWorkdays((int) nWorkdays);
        em.flush();

        return states;

    }

    @Override
    public boolean breakdownMonthPlannedDemands(Integer yearInt, Integer monthInt) {
        System.out.println("Generating (Breaking down) Weekly Daily Planned Demands for the month " + monthInt + "/" + yearInt);

        List<InventoryState> products = iib.viewProducts();

        try {
            for (InventoryState product : products) {
                this.generateWeeklyPlannedDemands(product.getItemId(), yearInt, monthInt);
                for (int i = 1; i <= 6; i++) {
                    this.generateDailyPlannedDemands(product.getItemId(), yearInt, monthInt, i);
                }
            }
        } catch (Exception e) {
            return false;
        }

        return true;
    }

    @Override //also a view method
    public List<PlannedDemandState> generateDailyPlannedDemands(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth) {
        System.out.println("Generating Daily Planned Demands...");
        PlannedWeeklyDemandEntity pwd = this.getPlannedWeeklyDemand(productId, yearInt, monthInt, weekOfMonth);

        if (pwd == null) {
            throw new NullPointerException("The corresponding planned weekly demand does not exist.");
        }

        if (pwd.isDecomposed()) {
            System.out.println("Planned Weekly Demands have previously been decomposed and finalized");
            List<PlannedDailyDemandEntity> pdds = new ArrayList();
            pdds = (List<PlannedDailyDemandEntity>) pwd.getPlannedDailyDemandEntities();
            if (pwd.getDayOfMonth().equals(99)) {
                pwd.setDayOfMonth(pdds.get(0).getDayOfMonth());
                em.flush();
            }
            List<PlannedDemandState> states = new ArrayList();
            for (PlannedDailyDemandEntity pdd : pdds) {
                PlannedDemandState state = new PlannedDemandState();
                state.createDailyPlannedDemandState(pdd.getId(), yearInt, monthInt, weekOfMonth, pdd.getDayOfMonth(), productId, pwd.getPlannedMonthlyDemand().getMonthlyDemand(), pdd.getAdjustedQty(), pwd.getPlannedMonthlyDemand().getnWorkdays());
                states.add(state);
            }
            return states;
        }


        int nWorkInWeek[] = this.getWorkingDaysInWeeksInMonth(yearInt, monthInt);
        int nWorkdays = nWorkInWeek[weekOfMonth - 1];
        int daysToDate = 0;

        for (int i = 1; i < weekOfMonth + 1; i++) {
            daysToDate += nWorkInWeek[i - 1];
        }
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, yearInt);
        cal.set(Calendar.MONTH, monthInt - 1);
        cal.set(Calendar.DAY_OF_MONTH, 1);
        System.out.println("Breaking down the planned weekly demands for date starting from " + cal.getTime().toString());
        Date endDateOfWeek = this.calculateEndDate(cal.getTime(), daysToDate);
        cal.setTime(endDateOfWeek);
        //cal.add(Calendar.DAY_OF_MONTH, -1);
        Integer dayInt = cal.get(Calendar.DAY_OF_MONTH);

        List<PlannedDailyDemandEntity> pdds = new ArrayList<PlannedDailyDemandEntity>();
        pdds = (List<PlannedDailyDemandEntity>) pwd.getPlannedDailyDemandEntities();
        PlannedDailyDemandEntity pdd;
        double weeklyDemand = (double) pwd.getWeeklyDemand();
        int dailyDemand = (int) (weeklyDemand / nWorkdays + 0.5);

        boolean toCreate = false;
        if (pdds.isEmpty()) {
            toCreate = true;
        }

        //setting the maximum quantity for the day production;
        int utilization = pwd.getPlannedMonthlyDemand().getConsolidatedMonthlySalesRecordEntity().getUtilization();
        int maxQty = (int) (137.5 * dailyDemand / utilization + 0.5);
        /*
         * Assumptions
         * 1) each line can only have a maximum of 2 changeover @30 mins
         * 2) each product can only be rushed in proportion to their preset priority (ratio)
         * 
         * The maximum utilization will be (8 normal shift hours + 4 overtime hours - 1 changeover hours)
         * i.e. max utilization = 137.5 % (11/8 *100%)
         * 
         * Thus, max quantity to be produced = max utilization * current demand / current utilization;
         * 
         */

        List<PlannedDemandState> states = new ArrayList();

        System.out.println("Generating the planned daily demands from broken down figures.. ");
        for (int i = 0; i < nWorkdays; i++) {
            System.out.println("For day: " + (dayInt + i - nWorkdays));
            if (toCreate) {
                pdd = new PlannedDailyDemandEntity(pwd.getProduct(), yearInt, monthInt, weekOfMonth, (dayInt + i - nWorkdays));
                em.persist(pdd);
            } else {
                pdd = this.getPlannedDailyDemand(productId, yearInt, monthInt, weekOfMonth, (dayInt + i - nWorkdays));
            }
            pdd.setDailyDemand(dailyDemand);
            pdd.setReservedQty(0);
            pdd.setMaxQty(maxQty);
            pdd.setAdjustedQty(dailyDemand);
            pdd.setPlannedWeeklyDemand(pwd);
            pdds.add(pdd);
            pwd.setPlannedDailyDemandEntities(pdds);
            if (i == 0) { // 1st day in week
                Date execDate = pdd.getExecDate();
                Calendar cal2 = Calendar.getInstance();
                cal2.setTime(execDate);
                pwd.setDayOfMonth(cal2.get(Calendar.DAY_OF_MONTH));
            }
            em.flush();
            PlannedDemandState state = new PlannedDemandState();
            state.createDailyPlannedDemandState(pdd.getId(), yearInt, monthInt, weekOfMonth, pdd.getDayOfMonth(), productId, pwd.getPlannedMonthlyDemand().getMonthlyDemand(), dailyDemand, pwd.getPlannedMonthlyDemand().getnWorkdays());
            states.add(state);
            System.out.println("Planned Daily Demand for " + pdd.getExecDate() + "is created");
            pdd = null;
        }
        em.flush();

        return states;
    }

    @Override
    public boolean commitMonthDailyPlannedDemand(Integer yearInt, Integer monthInt) {
        Query q = em.createQuery("SELECT a FROM PlannedDailyDemandEntity a where a.yearInt = :yearInt and a.monthInt = :monthInt");
        q.setParameter("yearInt", yearInt);
        q.setParameter("monthInt", monthInt);

        try {
            for (Object o : q.getResultList()) {
                PlannedDailyDemandEntity pdd = (PlannedDailyDemandEntity) o;

                if (!pdd.isCommitted()) {
                    pdd.setCommitted(true);
                    PlannedWeeklyDemandEntity pwd = pdd.getPlannedWeeklyDemand();
                    pwd.setDecomposed(true);
                    PlannedMonthlyDemandEntity pmd = pwd.getPlannedMonthlyDemand();
                    pmd.setDecomposed(true);
                    ProductEntity product = pdd.getProduct();
                    product.setOrderedQty(product.getOrderedQty() + pdd.getAdjustedQty());
                    em.flush();
                }
            }
        } catch (Exception e) {
            e.getCause();
            e.getMessage();
            return false;
        }

        return true;
    }

    protected boolean commitDailyPlannedDemand(Long productId, Integer yearInt, Integer monthInt, Integer weekOfMonth, Integer dayOfMonth) {
        PlannedDailyDemandEntity pdd = this.getPlannedDailyDemand(productId, yearInt, monthInt, weekOfMonth, dayOfMonth);

        if (!pdd.isCommitted()) {
            pdd.setCommitted(true);
            PlannedWeeklyDemandEntity pwd = pdd.getPlannedWeeklyDemand();
            pwd.setDecomposed(true);
            PlannedMonthlyDemandEntity pmd = pwd.getPlannedMonthlyDemand();
            pmd.setDecomposed(true);
            ProductEntity product = pdd.getProduct();
            product.setOrderedQty(product.getOrderedQty() + pdd.getDailyDemand());
            em.flush();
            return true;
        } else {
            return false;
        }
    }

    @Override
    public List<PlannedDemandState> viewPlannedDemands(int option) {

        String clause = "";

        switch (option) {
            case 1:
                clause = "SELECT pd FROM PlannedMonthlyDemandEntity pd";
                break;
            case 2:
                clause = "SELECT pd FROM PlannedWeeklyDemandEntity pd";
                break;
            case 3:
                clause = "SELECT pd FROM PlannedDailyDemandEntity pd";
                break;
        }

        Query q = em.createQuery(clause);
        List stateList = new ArrayList();

        switch (option) {
            case 1:
                for (Object o : q.getResultList()) {
                    PlannedMonthlyDemandEntity pd = (PlannedMonthlyDemandEntity) o;
                    PlannedDemandState pdState = new PlannedDemandState(pd.getId(), pd.getYearInt(), pd.getMonthInt(), pd.getWeekOfMonth(), pd.getDayOfMonth(), pd.getProduct().getId());
                    pdState.setMonthlyDemand(pd.getMonthlyDemand());
                    pdState.setnWorkdaysInMonth(pd.getnWorkdays());
                    stateList.add(pdState);
                }
                break;
            case 2:
                for (Object o : q.getResultList()) {
                    PlannedWeeklyDemandEntity pd = (PlannedWeeklyDemandEntity) o;
                    PlannedDemandState pdState = new PlannedDemandState(pd.getId(), pd.getYearInt(), pd.getMonthInt(), pd.getWeekOfMonth(), pd.getDayOfMonth(), pd.getProduct().getId());
                    pdState.setMonthlyDemand(pd.getPlannedMonthlyDemand().getMonthlyDemand());
                    pdState.setWeeklyDemand(pd.getWeeklyDemand());
                    pdState.setnWorkdaysInMonth(pd.getPlannedMonthlyDemand().getnWorkdays());
                    pdState.setnWorkdaysInWeek(pd.getnWorkdays());
                    stateList.add(pdState);
                }
                break;
            case 3:
                for (Object o : q.getResultList()) {
                    PlannedDailyDemandEntity pd = (PlannedDailyDemandEntity) o;
                    PlannedDemandState pdState = new PlannedDemandState(pd.getId(), pd.getYearInt(), pd.getMonthInt(), pd.getWeekOfMonth(), pd.getDayOfMonth(), pd.getProduct().getId());
                    pdState.setMonthlyDemand(pd.getPlannedWeeklyDemand().getPlannedMonthlyDemand().getMonthlyDemand());
                    pdState.setDailyDemand(pd.getDailyDemand());
                    pdState.setnWorkdaysInMonth(pd.getPlannedWeeklyDemand().getPlannedMonthlyDemand().getnWorkdays());
                    pdState.setnWorkdaysInWeek(pd.getPlannedWeeklyDemand().getnWorkdays());
                    stateList.add(pdState);
                }
                break;

        }

        return stateList;
    }

    /*
     * option:
     * 1 - Quotation
     * 2 - Sales Order
     */
    @Override
    public ATPCheckState calculateLeadTime(Long salesDocId, Long productId, int demand, Date checkingDate, int bufferDays,
            int indicativeLeadtime, int option) {

        Calendar cal = Calendar.getInstance();
        cal.setTime(checkingDate);
        ATPCheckState atp;
        int immediateAvailableQty = demand;
        int store = demand;
        System.out.println("MRP: getting lead time");
        ProductEntity product = em.find(ProductEntity.class, productId);
        SalesDocumentEntity sd = em.find(SalesDocumentEntity.class, salesDocId);
        BackOrderEntity bo = sd.getBackOrder();
        if (bo == null) {
            bo = new BackOrderEntity();
            em.persist(bo);
            bo.setSalesDocument(sd);
            sd.setBackOrder(bo);
            em.flush();
        }
        System.out.println("MRP: product and sales document fetched");
        List<ReservationEntity> reservations = new ArrayList();
        reservations = bo.getReservationEntities();

        //dropping previous reservations
        for (ReservationEntity reservation : reservations) {
            if (product.getId().equals(reservation.getProductId())) {
                System.out.println("Attempting to drop " + reservation.toString());
                pmb.withdrawReservation(reservation.getId());
            }
        }
        System.out.println("MRP: dropping previous reservations");

        /*
        QuotationEntity quotation = em.find(QuotationEntity.class, salesDocId);
        SalesOrderEntity salesOrder = em.find(SalesOrderEntity.class, salesDocId);
        
        switch (option) {
        case 1:
        if (product == null || quotation == null) {
        throw new NullPointerException();
        }
        break;
        case 2:
        if (product == null || salesOrder == null) {
        throw new NullPointerException();
        }
        break;
        default:
        return null;
        }
         */

        //Getting non-deleted, non-empty product batches sorted by mfgDate -- ensuring FIFO
        Query q = em.createQuery("SELECT b FROM InventoryBatchEntity b WHERE b.itemId = :productId AND b.inHandQty > 0 AND b.deleted <> :deleted ORDER BY b.mfgDate").setParameter("productId", productId).setParameter("deleted", true);
        List<InventoryBatchEntity> batches = new ArrayList();
        batches = (List<InventoryBatchEntity>) q.getResultList();

        /*
        for (InventoryBatchEntity batch : batches) {
        if (batch.getReservedQty() >= batch.getInitialQty()) {
        batches.remove(batch);
        }
        }
         */

        System.out.println("MRP: product batches fetched");
        //1st step: Inventory Check
        if (product.getInHandQty() >= demand) {
            while (demand > 0) {
                for (InventoryBatchEntity batch : batches) {
                    ReservationEntity reservation = new ReservationEntity();
                    int remainder = batch.getInHandQty();
                    if (demand > remainder) {
                        // demand is greater than the remaining unreserved, reserve the remaining
                        batch.setReservedQty(batch.getReservedQty() + remainder);
                        batch.setInHandQty(batch.getInHandQty() - remainder);
                        System.out.println("Step 1: Updating product, (in hand / reserved) = " + product.getInHandQty() + "/" + product.getReservedQty());
                        product.setReservedQty(product.getReservedQty() + remainder);
                        product.setInHandQty(product.getInHandQty() - remainder);
                        demand -= remainder;
                        reservation.reserveInventoryBatch(productId, remainder, bo, batch);
                        em.persist(reservation);
                        reservations = bo.getReservationEntities();
                        reservations.add(reservation);
                        bo.setReservationEntities(reservations);
                        System.out.println("Step 1: Reservation of " + remainder + " cases is put on inventory: " + reservation.toString());
                        em.flush();
                        System.out.println("Step 1: Product updated, (in hand / reserved) = " + product.getInHandQty() + "/" + product.getReservedQty());
                    } else {
                        //adjust reserved quantity
                        System.out.println("Step 1: Updating product, (in hand / reserved) = " + product.getInHandQty() + "/" + product.getReservedQty());
                        batch.setReservedQty(batch.getReservedQty() + demand);
                        product.setReservedQty(product.getReservedQty() + demand);
                        product.setInHandQty(product.getInHandQty() - demand);
                        batch.setInHandQty(remainder - demand);
                        reservation.reserveInventoryBatch(productId, demand, bo, batch);
                        em.persist(reservation);
                        reservations = bo.getReservationEntities();
                        reservations.add(reservation);
                        bo.setReservationEntities(reservations);
                        System.out.println("Step 1: Reservation of " + demand + " cases is put on inventory: " + reservation.toString());
                        em.flush();
                        System.out.println("Step 1: Product updated, (in hand / reserved) = " + product.getInHandQty() + "/" + product.getReservedQty());
                        demand = 0;
                    }
                }
            }

            //product.setOrderedQty(product.getOrderedQty() - demand);

            cal.add(Calendar.DAY_OF_MONTH, bufferDays);
            em.flush();
            System.out.println("LEAD TIME : " + cal.getTime());
            atp = new ATPCheckState(bufferDays, immediateAvailableQty, cal.getTime());
            System.out.println("MRP: return atp, enough inventory");
            return atp;
        }

        //2nd step: Insufficient in hand qty, reserve in-hand quantity and check planned demands
        System.out.println("MRP: not enough inventory, checking future planned demands");
        demand = store; //restore the demand for calculation

        //Reserving all in-hand inventory first
        for (InventoryBatchEntity batch : batches) {
            System.out.println("Step 2: Updating product, (in hand / reserved) = " + product.getInHandQty() + "/" + product.getReservedQty());
            ReservationEntity reservation = new ReservationEntity();
            int remainder = batch.getInHandQty();
            batch.setReservedQty(batch.getReservedQty() + remainder); // fully reserved
            product.setReservedQty(product.getReservedQty() + remainder);
            product.setInHandQty(product.getInHandQty() - remainder);
            batch.setInHandQty(batch.getInHandQty() - remainder);
            demand -= remainder;
            reservation.reserveInventoryBatch(productId, remainder, bo, batch);
            em.persist(reservation);
            reservations = bo.getReservationEntities();
            reservations.add(reservation);
            bo.setReservationEntities(reservations);
            System.out.println("Step 2: Reservation of " + remainder + " is put on inventory: " + reservation.toString());
            em.flush();
            System.out.println("Step 2: Product updated, (in hand / reserved) = " + product.getInHandQty() + "/" + product.getReservedQty());
        }

        immediateAvailableQty -= demand;
        System.out.println("Immediate Available Qty = " + immediateAvailableQty);
        Query q2 = em.createQuery("SELECT pd FROM PlannedDailyDemandEntity pd WHERE pd.execDate > :checkingDate ORDER BY pd.execDate").setParameter("checkingDate", checkingDate);
        //2a. check if future planned daily demands within indicative leadtime is sufficient

        //System.out.println("MRP: future planned demands fetched");

        List<PlannedDailyDemandEntity> pdds = new ArrayList();
        for (Object o : q2.getResultList()) {
            PlannedDailyDemandEntity pdd = (PlannedDailyDemandEntity) o;
            if (product.equals(pdd.getProduct())) {
                pdds.add(pdd);
            }
        }


        int qty = demand; // the remaining quantity to be optimized
        int nWorkdays = 0;
        int checkingSum = 0;

        indicativeLeadtime -= bufferDays; // discount the days in buffer from the passed indicative leadtime

        while (indicativeLeadtime > 0) {
            for (Object o : pdds) {
                PlannedDailyDemandEntity pdd = (PlannedDailyDemandEntity) o;
                int remainder = pdd.getAdjustedQty() - pdd.getReservedQty();
                checkingSum += remainder;
                indicativeLeadtime--;
            }
        }

        if (checkingSum > demand) {
            System.out.println("MRP: can meet indicative lead time, reserving future planned demands");
            for (Object o : pdds) {
                PlannedDailyDemandEntity pdd = (PlannedDailyDemandEntity) o;

                if (!pdd.isDepleted()) {
                    int availableQty;
                    if (pdd.getAdjustedQty() > pdd.getReservedQty()) {
                        availableQty = pdd.getAdjustedQty() - pdd.getReservedQty();
                        qty -= availableQty;

                        if (qty <= 0) {
                            cal.setTime(pdd.getExecDate());
                            cal.add(Calendar.DAY_OF_MONTH, bufferDays);
                            System.out.println("Step 2a: Reservation of " + (availableQty + qty) + "cases can be put on " + pdd.toString());
                            //commit only if enquired by sales order
                            if (option == 2) { // just a safeguard, when there is an indicative leadtime it means this method is called by a sales order
                                pdd.setReservedQty(pdd.getReservedQty() + availableQty + qty); //
                                if (pdd.getReservedQty() > 0) {
                                    pdd.setDepleted(false);
                                    ReservationEntity reservation = new ReservationEntity();
                                    reservation.reservePlannedDemand(productId, availableQty + qty, bo, pdd);
                                    em.persist(reservation);
                                    reservations = bo.getReservationEntities();
                                    reservations.add(reservation);
                                    bo.setReservationEntities(reservations);
                                    System.out.println("Step 2a: Reservation of " + (availableQty + qty) + "cases is put on planned demand: " + reservation.toString());
                                }
                            }
                            em.flush();
                            System.out.println("Returning leadtime: " + (nWorkdays + bufferDays));
                            atp = new ATPCheckState(nWorkdays + bufferDays, immediateAvailableQty, cal.getTime());
                            return atp;
                        }
                        System.out.println("Step 2a: Reservation of " + (availableQty) + "cases can be put on " + pdd.toString());
                        //commit only if enquired by sales order
                        if (option == 2) {
                            pdd.setReservedQty(pdd.getAdjustedQty());
                            pdd.setDepleted(true);
                            ReservationEntity reservation = new ReservationEntity();
                            reservation.reservePlannedDemand(productId, availableQty, bo, pdd);
                            em.persist(reservation);
                            reservations = bo.getReservationEntities();
                            reservations.add(reservation);
                            bo.setReservationEntities(reservations);
                            System.out.println("Step 2a: Reservation of " + availableQty + "cases is put on planned demand: " + reservation.toString());
                        }
                    }

                    System.out.println("Step 2a: " + nWorkdays + "-th day, remaining quantity to reserve = " + qty);
                    nWorkdays++;
                } else {
                    System.out.println("The planned demand for " + nWorkdays + "th is unavailable, checking next day planned demand");
                }
            }
        }

        //2b.   future planned demand can't meet indicative lead time, check material
        //      check material availability and capacity
        if (option == 2) {
            System.out.println("MRP: can't meet indicative lead time, checking materials");
        }
        /*
         * 
         * Preparing for material & capacity check
         * The limiting material will be the material with most quantity required per batch of BOM (200 kg of products)
         * that is Oats (100 kg for classic, 120 kg for power bar)
         * 
         * 1 batch of classic bars contain 16.7 case
         * 1 batch of premium bars contain 15 case
         * 
         * During material requirements planning, we have set floor requirements at 300 unit per materials
         * as such, for each week that passes we assume another 300 kg of Oats in excess is delivered 
         * 
         * after n weeks of expected leadtime, we  remove the material limit as 
         * we can basically order the material today to fulfill the nth week requirements.
         */

        Long materialId = iib.searchMaterialId("Oats");
        MaterialEntity material = iib.searchMaterial(materialId);

        int inHandQtyRaw = material.getInHandQty();
        double multiplier = 0;
        int limit = 0; // limit as allowed by material in inventory;
        int maxQty = 0; // the ceiling put by the daily production plan;
        int allowedQty = 0; // the allowed qty for unplanned production;
        int topUp = 0; // the weekly expected excess 
        String productName = product.getName();
        int leadTime = material.getLeadTime();

        if (productName.startsWith("Classic")) {
            multiplier = inHandQtyRaw / 100;
            limit = (int) (multiplier * 16.7 + 0.5);
            topUp = (int) (3 * 16.7 + 0.5);
        } else if (productName.startsWith("Premium")) {
            multiplier = inHandQtyRaw / 120;
            limit = (int) (multiplier * 15 + 0.5);
            topUp = (int) (2.5 * 16.7 + 0.5);
        }

        qty = demand;
        nWorkdays = 0;

        Integer weekInt = 9;

        for (Object o : pdds) {
            PlannedDailyDemandEntity pdd = (PlannedDailyDemandEntity) o;
            maxQty = pdd.getMaxQty();
            if (weekInt.intValue() == 9) {
                weekInt = pdd.getWeekOfMonth();
                System.out.println("Getting daily plans of week " + weekInt);
            }

            if (weekInt.intValue() != pdd.getWeekOfMonth().intValue()) {
                if (leadTime > 0) {
                    System.out.println("lead time for materials to be procured (lifting limit on material) = " + leadTime + " weeks");
                    limit += topUp; // for the first nth weeks (depend on leadtime of material), limit production to the expected excess material
                } else {
                    System.out.println("limit lifted");
                    limit = 99999; // from nth week onwards, only the daily production capacity will limit the unplanned demand production allowance
                }
                weekInt = pdd.getWeekOfMonth();
                System.out.println("Getting daily plans of week " + weekInt);

                leadTime--;
            }
            System.out.println("Remaining qty to be reserved = " + qty);
            if (!pdd.isDepleted()) {
                int availableQty;
                if (pdd.getAdjustedQty() > pdd.getReservedQty()) {
                    availableQty = pdd.getAdjustedQty() - pdd.getReservedQty();
                    qty -= availableQty;

                    if (qty <= 0) {
                        cal.setTime(pdd.getExecDate());
                        cal.add(Calendar.DAY_OF_MONTH, bufferDays);
                        System.out.println("Step 2b: Reservation of " + (availableQty + qty) + " cases can be put on " + pdd.toString());

                        //commit only if enquired by sales order
                        if (option == 2) {
                            pdd.setReservedQty(pdd.getReservedQty() + availableQty + qty); //
                            if (pdd.getReservedQty() > 0) {
                                pdd.setDepleted(false);
                                ReservationEntity reservation = new ReservationEntity();
                                reservation.reservePlannedDemand(productId, availableQty + qty, bo, pdd);
                                em.persist(reservation);
                                reservations = bo.getReservationEntities();
                                reservations.add(reservation);
                                bo.setReservationEntities(reservations);
                                System.out.println("Step 2b: Reservation of " + (availableQty + qty) + " cases is put on planned demand: " + reservation.toString());
                            }
                        }
                        em.flush();
                        System.out.println("Returning leadtime: " + nWorkdays + ", days in buffer: " + bufferDays + " = " + (nWorkdays + bufferDays));
                        atp = new ATPCheckState(nWorkdays + bufferDays, immediateAvailableQty, cal.getTime());
                        return atp;
                    }

                    System.out.println("Step 2b: Reservation of " + (availableQty) + "cases can be put on " + pdd.toString());
                    //commit only if enquired by sales order
                    if (option == 2) {
                        pdd.setReservedQty(pdd.getAdjustedQty());
                        pdd.setDepleted(true);
                        ReservationEntity reservation = new ReservationEntity();
                        reservation.reservePlannedDemand(productId, availableQty, bo, pdd);
                        em.persist(reservation);
                        reservations = bo.getReservationEntities();
                        reservations.add(reservation);
                        bo.setReservationEntities(reservations);
                        System.out.println("Step 2b: Reservation of " + availableQty + "cases is put on planned demand: " + reservation.toString());
                    }
                }
            } else {
                System.out.println("The planned demand for " + nWorkdays + "th is unavailable, checking capacity and material availability for unplanned demand");
            }

            // limit the creation of unplannedDailyDemand, will only be effected on confirmation of SO
            maxQty = pdd.getMaxQty();
            allowedQty = maxQty - pdd.getAdjustedQty();
            if (allowedQty > limit) {
                allowedQty = limit;
            }
            limit -= allowedQty; //decrement the material limit

            qty -= allowedQty;


            if (qty < 0) {
                System.out.println("Step 2b: " + (allowedQty + qty) + " of unplanned cases can be raised on " + nWorkdays + "-th day, remaining quantity to reserve = 0");
                if (option == 2) {
                    System.out.println("Raising unplanned demand of qty = " + (allowedQty + qty));
                    this.createUnplannedDailyDemand(bo, pdd, (allowedQty + qty));
                }
                break;
            }

            System.out.println("Step 2b: " + allowedQty + " of unplanned cases can be raised on " + nWorkdays + "-th day, remaining quantity to reserve = " + qty);
            if (option == 2) {
                System.out.println("Raising unplanned demand of qty = " + allowedQty);
                this.createUnplannedDailyDemand(bo, pdd, allowedQty);
            }

            nWorkdays++;
        }

        System.out.println("Returning leadtime: " + nWorkdays + ", days in buffer: " + bufferDays + " = " + (nWorkdays + bufferDays));
        atp = new ATPCheckState(nWorkdays + bufferDays, immediateAvailableQty, cal.getTime());
        return atp;
    }
}
